package com.jacky.crypto;

import android.text.TextUtils;
import android.util.Base64;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import io.flutter.Log;

/**
 * Created by jacky on 2018/10/31.
 *
 * 加密解密算法集合
 */
public @interface EDA {

    class RSA{
        private static final String ALGORITHM = "RSA";
        public static String encrypt(String data, String publicKey, boolean hex) {
            byte[] bytes;
            try {
                byte[] keyBytes = Base64.decode(publicKey,Base64.DEFAULT);
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
                KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
                RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(keySpec);

                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, key);
                bytes = ciphers(cipher, data.getBytes(StandardCharsets.UTF_8), Cipher.ENCRYPT_MODE);
            } catch (Exception e) {
                Log.e("EDA", "", e);
                return "";
            }
            if(hex) {
                return Formatter.transferByte2Hex(bytes);
            }
           // return android.util.Base64.encodeToString(bytes, android.util.Base64.DEFAULT);
            return com.jacky.crypto.Base64.encode(bytes);
        }

        public static String decrypt(String content, String privateKey, boolean hex) {
            byte[] encrypted1 = hex ?
                    Formatter.transferHex2Byte(content) :
                    android.util.Base64.decode(content.getBytes(), android.util.Base64.DEFAULT);

            try {
                //私钥解密
                KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
                PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(com.jacky.crypto.Base64.decode(privateKey));
                RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);

                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, key);
                byte[] original = ciphers(cipher, encrypted1, Cipher.DECRYPT_MODE);
                return new String(original, StandardCharsets.UTF_8);
            } catch (Exception e) {
                Log.e("EDA", "", e);
            }
            return "";
        }

        /**
         * @param cipher 加解密对象
         * @param data   数据
         * @param mode   模式：加密(Cipher.ENCRYPT_MODE)或解密(Cipher.DECRYPT_MODE)
         * @return 加密或解密后数据
         * 功能描述 分段加解密
         */
        private static byte[] ciphers(Cipher cipher, byte[] data, int mode) throws Exception {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] subResult;
            if (Cipher.DECRYPT_MODE == mode) {// 解密
                for (int i = 0; i < data.length; i += 128) {
                    subResult = cipher.doFinal(subarray(data, i, i + 128));
                    baos.write(subResult);
                }
            } else if (Cipher.ENCRYPT_MODE == mode) {// 加密
                for (int i = 0; i < data.length; i += 64) {
                    subResult = cipher.doFinal(subarray(data, i, i + 64));
                    baos.write(subResult);
                }
            }
            return baos.toByteArray();
        }

        private static byte[] subarray(byte[] array, int startIndexInclusive, int endIndexExclusive) {
            if(array == null) {
                return null;
            } else {
                if(startIndexInclusive < 0) {
                    startIndexInclusive = 0;
                }
                if(endIndexExclusive > array.length) {
                    endIndexExclusive = array.length;
                }
                int newSize = endIndexExclusive - startIndexInclusive;
                if(newSize <= 0) {
                    return new byte[0];
                } else {
                    byte[] subarray = new byte[newSize];
                    System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
                    return subarray;
                }
            }
        }
    }

    class AES {
        private static final String CipherMode = "AES/GCM/NoPadding";
        private static final String AES = "AES";
        private static final int keyLength = 256;
        private static final int GCM_TAG_LENGTH = 128;// in bytes

        public static String encrypt(String content, String password, boolean hex) {
            try {
                byte[] pwd = password.getBytes(StandardCharsets.UTF_8);
                SecretKeySpec keyspec = getKey(pwd);
                byte[] iv = Arrays.copyOfRange(pwd, 0, 12);
                GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH , iv);

                Cipher cipher = Cipher.getInstance(CipherMode);
                cipher.init(Cipher.ENCRYPT_MODE, keyspec, spec);
                byte[] encrypted = cipher.doFinal(content.getBytes());
                if(hex) {
                    return Formatter.transferByte2Hex(encrypted);
                }
                return android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
            } catch (Exception e) {
                Log.e("EDA", "", e);
                return null;
            }
        }

        public static String decrypt(String content, String password, boolean hex) {
            try {
                byte[] encrypted1 = hex ?
                        Formatter.transferHex2Byte(content) :
                        android.util.Base64.decode(content.getBytes(), android.util.Base64.DEFAULT);

                byte[] pwd = password.getBytes(StandardCharsets.UTF_8);
                SecretKeySpec keyspec = getKey(pwd);
                byte[] iv = Arrays.copyOfRange(pwd, 0, 12); // 假设IV是前12字节
                GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH , iv);

                Cipher cipher = Cipher.getInstance(CipherMode);
                cipher.init(Cipher.DECRYPT_MODE, keyspec, spec);
                byte[] original = cipher.doFinal(encrypted1);
                return new String(original, StandardCharsets.UTF_8);
            } catch (Exception e) {
                Log.e("EDA", "", e);
                return null;
            }
        }

        private static SecretKeySpec getKey(byte[] password) {
            byte[] bs = Arrays.copyOf(password, keyLength / 8);
            return new SecretKeySpec(bs, AES);
        }
    }

    class SHA256 {

        public static String sha256_mac(String string, String key, boolean hex) {
            try {
                Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
                SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(),"HmacSHA256");
                sha256_HMAC.init(secret_key);
                byte[] bytes = sha256_HMAC.doFinal(string.getBytes());
                if(hex) {
                    return Formatter.transferByte2Hex(bytes);
                } else {
                    return Base64.encodeToString(bytes, Base64.DEFAULT);
                }
            } catch (Exception e) {
                Log.e("EDA", "", e);
            }
            return null;
        }
        public static String digest(String string) {
            return TextUtils.isEmpty(string) ? "" : digest(string.getBytes());
        }
        public static String digest(File file) {
            return MD5.digest("SHA-256", file, 1024 * 2);
        }
        public static String digest(byte[] bytes) {
            if (bytes == null || bytes.length == 0) return "";

            byte[] b = MD5.digest_algorithm("SHA-256", bytes);
            return Formatter.transferByte2Hex(b);
        }
    }

    class MD5 {
        /**
         * 获取字符串信息摘要
         *
         * @return 返回经过MD5算法加密后的字符串。如果string为null或空，则返回空字符串
         */
        public static String digest(String string) {
            return TextUtils.isEmpty(string) ? "" : digest(string.getBytes());
        }

        /**
         * 获取字符串信息摘要
         *
         * @return 返回经过MD5算法加密后的字符串
         */
        public static String digest(byte[] bytes) {
            if (bytes == null || bytes.length == 0) return "";

            byte[] b = digest_algorithm("MD5", bytes);
            return Formatter.transferByte2Hex(b);
        }

        /**
         * 获取文件的信息摘要
         */
        public static String digest(File file) {
            return digest("MD5", file, 1024 * 2);
        }
        /**
         *  获取文件的信息摘要
         */
        public static String digest(String algorithm, File file, int blockSize) {
            if(file == null || !file.exists()) return "";

            try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file))) {
                MessageDigest mdTemp = MessageDigest.getInstance(algorithm);
                int i;
                byte[] bytes = new byte[blockSize];
                while (true) {
                    i = stream.read(bytes);
                    if (i == -1) break;
                    mdTemp.update(bytes, 0, i);
                }
                byte[] b = mdTemp.digest();
                return Formatter.transferByte2Hex(b);
            } catch (Exception e) {
                Log.e("EDA", "", e);
                return "";
            }
        }

        /**
         * 获取字符串信息摘要
         */
        private static byte[] digest_algorithm(String algorithm, byte[] bytes) {
            try {
                MessageDigest mdTemp = MessageDigest.getInstance(algorithm);
                mdTemp.update(bytes);
                return mdTemp.digest();
            } catch (NoSuchAlgorithmException e) {
                Log.e("EDA", "", e);
                return bytes;
            }
        }
    }

    class Formatter {
        private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        /**
         * 将16进制格式的字符串转换为byte数组
         */
        public static byte[] transferHex2Byte(String string) {
            int size = TextUtils.isEmpty(string) ? 0 : string.length();
            byte[] bytes = new byte[size/2];
            if(size == 0) return bytes;

            char[] chars = string.toCharArray();
            for(int i = 0;i < bytes.length;i++) {
                char c1 = chars[i * 2];
                int i1 = c1 >= 'a' ? (c1 - 'a' + 10) : (c1 - '0');

                c1 = chars[i * 2 + 1];
                int i2 = c1 >= 'a' ? (c1 - 'a' + 10) : (c1 - '0');
                bytes[i] = (byte) ((i1 << 4) + i2);
            }
            return bytes;
        }

        public static String encodeBase64(String string) {
            if(TextUtils.isEmpty(string)) return "";
            return Base64.encodeToString(string.getBytes(), Base64.DEFAULT);
        }

        public static String decodeBase64(String string) {
            if(TextUtils.isEmpty(string)) return "";
            return new String(Base64.decode(string, Base64.DEFAULT));
        }

        /**
         * 将byte数组转化为十六进制的字符
         */
        public static String transferByte2Hex(byte[] bytes) {
            int k = 0;
            int size = bytes == null ? 0 : bytes.length;
            char[] str = new char[size * 2];

            for (int i = 0; i < size; i++) {
                byte b = bytes[i];
                str[k++] = hexDigits[b >> 4 & 0xf];
                str[k++] = hexDigits[b & 0xf];
            }
            return new String(str);
        }
        /**
         * 获取字符串的哈希值
         */
        public static String getHashcodeString(String string) {
            if (TextUtils.isEmpty(string)) return "";
            long hashcode = 0;
            int size = string.length();
            for (int i = 0; i < size; i++) {
                hashcode = 131 * hashcode + string.charAt(i);
            }
            return Long.toHexString(hashcode);
        }
    }
}